﻿using System;
using System.Collections.Generic;
using PostSharp.Aspects;
using Sapit.Attributes.Interfaces;
using Sapit.Helpers;

namespace Sapit.PostSharp.Attributes
{
    /// <summary>
    /// Apply this attribute to methods that should have a breaker switch applied to them.
    /// </summary>
    [Serializable]
    public class CircuitBreakerAttribute : MethodInterceptionAspect, ICircuitBreaker
    {
        /// <summary>
        /// The maximum number of consecutive failed attempts to call the method
        /// </summary>
        public int MaxConsecutiveFailures { get; set; }
        /// <summary>
        /// The number of seconds to shortcircuit the method when the maximum number of consecutive failures is reached
        /// </summary>
        public int ShutOffPeriodSeconds { get; set; }
        /// <summary>
        /// Defines the behavior when the external dependency is switched off, return a default value or throw an exception
        /// </summary>
        public Behavior BehaviorWhenShutOff { get; set; }
        /// <summary>
        /// The value to return when the method is switched off
        /// </summary>
        public object ShutOffReturnValue { get; set; }

        [NonSerialized]
        private static Dictionary<Invocation, TimedValue<int>> failedInvocations = new Dictionary<Invocation, TimedValue<int>>();

        public override void OnInvoke(MethodInterceptionArgs args)
        {
            Invocation inv = new Invocation() { InvocationTarget = args.Instance, Method = args.Method };

            if (ThisInvocationIsShutOff(inv))
            {
                if (ThisInvocationShouldBeTurnedBackOn(inv))
                {
                    TurnThisInvocationBackOn(inv);
                    TryProceed(args, inv);
                }
                else
                {
                    PerformShutOffBehavior(args);
                }
            }
            else
            {
                TryProceed(args, inv);
            }
        }

        private bool ThisInvocationIsShutOff(Invocation invocation)
        {
            return
                failedInvocations.ContainsKey(invocation) && // The invocation failed last time
                failedInvocations[invocation].Value >= MaxConsecutiveFailures; // The invocation has exceeded max consecutive failures
        }

        private bool ThisInvocationShouldBeTurnedBackOn(Invocation inv)
        {
            return
                failedInvocations[inv].When.AddSeconds(ShutOffPeriodSeconds) < DateTime.Now; // The invocation is in the shut off period
        }

        private void TurnThisInvocationBackOn(Invocation invocation)
        {
            failedInvocations.Remove(invocation);
        }

        private void PerformShutOffBehavior(MethodInterceptionArgs args)
        {
            switch (BehaviorWhenShutOff)
            {
                case Behavior.Undefined:
                case Behavior.ReturnDefaultValue:
                    args.ReturnValue = ShutOffReturnValue;
                    break;
                case Behavior.ThrowException:
                    throw new InvalidOperationException(string.Format("{0}", ShutOffReturnValue));
                default:
                    args.ReturnValue = ShutOffReturnValue;
                    break;
            }
        }

        private void TryProceed(MethodInterceptionArgs args, Invocation inv)
        {
            try
            {
                args.Proceed();
            }
            catch
            {
                RegisterFailure(inv);
                throw;
            }
        }

        private void RegisterFailure(Invocation invocation)
        {
            if (failedInvocations.ContainsKey(invocation))
            {
                failedInvocations[invocation].Value += 1;
            }
            else
            {
                failedInvocations.Add(invocation, new TimedValue<int>() { Value = 1, When = DateTime.Now });
            }
        }
    }
}
